package com.probiz.estore.core.controller;

import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.displaytag.tags.TableTagParameters;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.validation.BindException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.servlet.ModelAndView;

import com.opensymphony.oscache.util.StringUtil;
import com.probiz.estore.core.exception.ApplicationException;
import com.probiz.estore.core.model.BaseObject;
import com.probiz.estore.core.search.SearchCriteria;
import com.probiz.estore.core.service.GenericManager;
import com.probiz.estore.core.util.GenericsUtils;
import com.probiz.estore.core.util.UrlUtil;
import com.probiz.estore.webapp.util.RequestUtil;

/**
 * 只用于前台会员中心的Controller基类
 * 
 * 从GenericStoreAdminController抄过来，已尽量精简里面的方法
 * 
 * @since V3.5
 * @see GenericStoreAdminController
 */
public abstract class GenericCustomerFrontController<T> extends BaseController {

	/**
	 * 缺省每页显示多少条记录，子类可在配置文件根据需要设置。缺省10。
	 */
	protected int		defaultPageSize				= 10;
	
	/**
	 * 表单（Command）类型
	 */
	protected Class<T>			entityClass;	
	/**
	 * 操作表单的manager
	 */
	protected GenericManager<T>	mgr;

	/**
	 * Controller主要操作的实体的类名，框架会自动使用范型判断，一般不需要设置；如果自动判断失败，可以配置或在初始化设置。
	 */
	protected String	entityClassName;

	/**
	 * Controller主要操作的实体id（数据库和hibernate主键）的字段名，缺省是：{entityClassName}Id。
	 */
	protected String	entityIdName;
	
	/**
	 * 表单页面的model在request里面映射的参数名，框架缺省设置为{entityClassName}
	 */
	protected String	formModelName;
	
	/**
	 * 列表页面的model在request里面映射的参数名，框架缺省设置为{entityClassName}List;其数据类型一般是List。
	 */
	protected String	listModelName;

	/**
	 * 编辑页面使用的View，缺省是{lastPackageName}/{entityClassName}Form
	 */
	protected String	formView;
	
	/**
	 * 列表页面使用的View，缺省是{lastPackageName}/{entityClassName}List
	 */
	protected String	listView;
	
	/**
	 * 在表单页面，取消编辑以及删除成功的时候转向的View，缺省是列表页。
	 */
	protected String	cancelFormView;

	
	/**
	 * 在列表页面取消时返回的上级模块的view，缺省是重定向到/{lastPackageName}/index.html
	 */
	protected String	parentView;

	/**
	 * 在表单页面，保存成功后转向的View。缺省是{formView}，即保存后留在表单页。注意：saveAndNext即使成功还是会使用formView。
	 */
	protected String	successView;
	
	/**
	 * 在urlMapping映射的url，缺省是/{lastPackageName}/{entityClassName}.html
	 */
	protected String	mappedUrl;

	
	/**
	 * 是否允许保存上次搜索条件和结果（只是id）。后台缺省是启用。注意：允许会导致session里面保存很多数据，所以对于前台应用应该禁用。
	 */
	protected boolean	lastSearchViewAllowed		= true;

	//private 
	/**
	 * 这里进行数据的初始化，根据约定的规则。如果要用自定义的规则，可以通过子类构造函数、init方法、setter方法等进行修改。
	 */
	public GenericCustomerFrontController() {
		entityClass = GenericsUtils.getSuperClassGenricType(getClass());
	}
	
	/**
	 * 子类需要重载这个方法来实现初始化，包括设置范型mgr
	 * 
	 */
	protected abstract void initController() throws Exception;
	
	/**
	 * 子类需要重载这个方法来实现告诉框架，Entity实例的名称（可支持I18n）是什么
	 * @param entity
	 * @return
	 */
	protected abstract String getEntityName(T entity);
	

	/**
     * 令到Spring在完成设置后调用，并进一步调用各Controller的初始化方法
     * 这里会初始化所有常用的变量值。
     * @throws Exception
     */
    public final void afterPropertiesSet() throws Exception {
        if (StringUtil.isEmpty(entityClassName))
            entityClassName = entityClass.getSimpleName().substring(0, 1).toLowerCase()  + entityClass.getSimpleName().substring(1);
        // 缺省的View设置，注意，页面首字母小写，如system/SystemLanguageList。可以在Bean设置以覆盖。
        String viewPrefix = new StringBuilder(entityClass.getPackage().getName().substring(entityClass.getPackage().getName().lastIndexOf(".") + 1)).append("/").append(entityClassName).toString();
        if (StringUtil.isEmpty(listView))
            listView = viewPrefix + "List";
        if (StringUtil.isEmpty(formView))
            formView = viewPrefix + "Form";
        if (StringUtil.isEmpty(listModelName))
            listModelName = entityClassName + "List";
        if (StringUtil.isEmpty(formModelName))
            formModelName = entityClassName;
        if (StringUtil.isEmpty(entityIdName))
            entityIdName = entityClassName + "Id";
        if (StringUtil.isEmpty(mappedUrl))
            mappedUrl = "/" + viewPrefix + ".html";
        if (StringUtil.isEmpty(cancelFormView))
            cancelFormView = "redirect:" + mappedUrl;
        if (StringUtil.isEmpty(parentView))
            parentView = "redirect:" + UrlUtil.getParentUrl(mappedUrl);
        super.afterPropertiesSet();
    }
	/**
	 * 绑定并验证
	 * @param request
	 * @param command
	 * @return
	 * @throws ServletRequestBindingException
	 */
	protected final ServletRequestDataBinder bindAndValidate(HttpServletRequest request, Object command){
		return bindAndValidate(request, command, formModelName);
	}
	/**
	 * 绑定
	 * @param request
	 * @param command
	 * @return
	 * @throws ServletRequestBindingException
	 */
	protected final ServletRequestDataBinder bindOnly(HttpServletRequest request, Object command){
		return bindOnly(request, command, formModelName);
	}
	/**
	 * 获得默认binder,一般不需要绑定也不需要验证
	 * @param request
	 * @param command
	 * @return
	 */
	protected final ServletRequestDataBinder getDefaultBinder(HttpServletRequest request, Object command){
		ServletRequestDataBinder binder = new ServletRequestDataBinder(command,	formModelName);
		initBinder(request, binder);
		return binder;
	}	


	/**
	 * 缺省的SC创建方法。如果需要指定（限定）Filter，可在search前通过在request里面指定。
	 * <P>
	 * 如果不需要限定，框架会自动从parameter里面分析用户的选择（如果没有安全性要求且页面上有输出的话），如果也找不到，缺省为default
	 * 子类可以覆盖
	 * 
	 * @param request
	 * @return
	 */
	protected SearchCriteria createSearchCriteria(HttpServletRequest request) {
		return createSearchCriteria(request, BaseObject.FilterHqlName.DEFAULT);
	}
	
	protected SearchCriteria createSearchCriteria(HttpServletRequest request, Enum filterHqlName) {
		return createSearchCriteria(request, filterHqlName, null);
	}
	
	protected SearchCriteria createSearchCriteria(HttpServletRequest request, Object[] initHqlParamValues) {
		return createSearchCriteria(request, BaseObject.FilterHqlName.DEFAULT, initHqlParamValues);
	}

	/**
	 * 根据Request自动创建SC，并可保存在Session，以便重用。SC里面也会保存Request原来的参数名和值（包括Filter和Column），页面必须使用SC里面保存的参数来绑定，而不是request里面的参数。
	 * <P>
	 * 一般有以下几种情况：
	 * <ol>
	 * <li>第一次请求，没有用户输入的搜索条件，使用缺省的搜索条件（使用名为DEFAULT的filter对应的hql）</li>
	 * <li>对于只是翻页，不会创建新的SearchCriteria，只是更换页码</li>
	 * <li>对于新的搜索（不是returnToSearch或翻页），生成新的SC，不理是否与旧的SC相同，根据Filter，Column，Order等创建动态的SC</li>
	 * <li>每次会在session保存上一个搜索用的SC。注意：只是保存搜索条件，以及搜索结果的ID，实际的结果并不会保存，避免出现脏数据。</li>
	 * <li>对于在页面通过点击“返回上次搜索”，将重用session里面的SC</li>
	 * <li>对于点击连接等，也是当新的搜索处理，因为没有用户搜索条件，所以用缺省搜索条件</li>
	 * <li>通过浏览历史返回某搜索结果页面，由于搜索条件已经被更新，所以使用的是缺省搜索</li>
	 * <li>对于列表排序是全局排序，原理是修改sc中hql的order by，包含了两种方式的搜索，一种是旧搜索框架+displaytag，子类需要覆盖getSortMap方法；另外一种是旧搜搜框架+table，子类需要覆盖getSortMap4Direction方法</li>
	 * </ol>
	 * <P>
	 * 注意：View层应保证所有条件，包括缺省条件可以正确绑定，并且如果该缺省条件是用户不能修改的，应该用隐藏域绑定。
	 * 
	 * @param request
	 * @param in_filterName   可指定使用的filter，从而不同的action可以使用不同的filter；缺省是#BaseObject.FilterHqlNameEnum.DEFAULT
	 * @param initHqlParamValues
	 * @return
	 */
	protected SearchCriteria createSearchCriteria(HttpServletRequest request, Enum filterHqlName,Object[] initHqlParamValues) {
		SearchCriteria sc = null;
		//是否新的搜索，通过页面传入<NEW_SEARCH>来决定
		boolean isNewSearch = request.getParameter(ControllerConstants.NEW_SEARCH) != null;
		
		//【part1】=============== 处理SearchCriteria =====================================
		if(!isNewSearch  && lastSearchViewAllowed){
			//满足条件：不是新搜索+controller允许使用上一次搜索条件  那么就从session中获得上一次搜索条件。
			sc = this.getSavedSearchCriteria(request);
		}

		if (isNewSearch || null == sc) {
			//满足条件：新的搜索 或者 找不到保存过的SearchCriteria
			sc = mgr.getSearchCriteriaBuilder(filterHqlName==null?BaseObject.FilterHqlName.DEFAULT:filterHqlName,initHqlParamValues).buildSearchCriteria(request, defaultPageSize);
			
			//设置没有使用了上次搜索条件
			sc.setLastSearchCriteriaUsed(false);
			
		} else {
			//设置使用了上次搜索条件
			sc.setLastSearchCriteriaUsed(true);
			logger.debug("Reusing a saved SearchCriteria.");
		}
		
		//【part2】=============== 处理分页 =====================================
		if(isNewSearch){
			//新查询
			sc.changePaging(1, sc.getPageSize());
		}else{
			//翻页
			Integer prmPageNo = RequestUtil.getInteger(request,ControllerConstants.PRM_PAGENO);
			if (null!=sc && null!=prmPageNo) {
				if(prmPageNo <= 0){
					prmPageNo = 1;
				}
				Integer prmItemsPerPage = RequestUtil.getInteger(request,ControllerConstants.PRM_ITEMS_PER_PAGE);
				if(null != prmItemsPerPage){
					sc.changePaging(prmPageNo,prmItemsPerPage);
				}else{
					sc.changePaging(prmPageNo,sc.getPageSize());
				}
				
			}
		}
		
		
		//【part3】=============== 善后处理 =====================================
		//保存SearchCriteria到session
		saveSearchCriteria(request, sc);
		
		// 页面层需要用request（而不是session）里面的sc进行绑定（需要绑定column，filter，order等）
		request.setAttribute("sc", sc);
		
		return sc;
	}
	
	/**
	 * 从session中获得SearchCriteria
	 * @param request
	 * @return
	 */
	protected final SearchCriteria getSavedSearchCriteria(HttpServletRequest request){
		SearchCriteria sc = (SearchCriteria)request.getSession().getAttribute(ControllerConstants.SAVED_SEARCHCRITERIA);
		//URI不同，则删除，一个session只保存一个sc原则
		if(sc!=null && !request.getRequestURI().equals(sc.getSavedUri())){
			request.getSession().removeAttribute(ControllerConstants.SAVED_SEARCHCRITERIA);
			sc = null;
		}
			
		return sc;
	}

	/**
	 * 保存SearchCriteria到session
	 * @param request
	 * @param sc
	 */
	protected final void saveSearchCriteria(HttpServletRequest request, SearchCriteria sc){
		// 如果是导出的查询，跳过不用保存
		if (request.getParameter(TableTagParameters.PARAMETER_EXPORTING) != null)
			return;
		if (!lastSearchViewAllowed) {
			return;
		}
		//保存，一个 session只保存一个sc，按管理员单个列表页窗口作业情况
		sc.setSavedUri(request.getRequestURI());
		request.getSession().setAttribute(ControllerConstants.SAVED_SEARCHCRITERIA, sc);
	}
	/**
	 * 缺省Action,列出缺省搜索条件的搜索结果列表。必须转给search处理。
	 * @param request
	 * @param response
	 * @return
	 * @throws ServletException
	 */
	public ModelAndView defaultAction(HttpServletRequest request, HttpServletResponse response) {
		return search(request, response);
	}
	
	/**
	 * 缺省、统一、规范的搜索入口。SearchCriteria可以继续扩展支持Hql、Hibernate
	 * Criteria、JDBC等不同的实现。但Hql无疑最简单，推荐。
	 * <P>
	 * 一般来说所有的搜索最终都应该调用这个方法。对于复杂列表页面，很可能还需要其他Model，可以重载或实现自己的Action，调用search得到mv，然后处理并添加新的model。
	 * <P>
	 * returnToSearch的时候，只会返回此方法（除非覆盖了）；但因为使用的是保存的SC，所以不传入filterName也可正常工作。
	 * 
	 * @param request
	 * @param response
	 * @return
	 */
	public ModelAndView search(HttpServletRequest request, HttpServletResponse response) {
		SearchCriteria sc = createSearchCriteria(request);
		List results = searchByCriteria(sc);
		return getModelAndView(listView, listModelName, results);
	}

	/**
	 * 根据SearchCriteria进行搜索。可以使用createSearchCriteria等预建的方法快速、自动构造SC，也可以用自己定制的SC。
	 * 
	 * @param sc
	 * @return
	 */
	protected List searchByCriteria(SearchCriteria sc) {
	    if (sc.getPageNo() < 1)  //如果是翻页过前，就拿第一页就可以。
        {
            sc.changePaging(1, sc.getPageSize());
        }
	    List result = mgr.searchByCriteria(sc);
	    if (result == null|| result.size() == 0 )
	    {
	        if (sc.getPageNo() > sc.getTotalPageCount()) //如果是翻页前，就拿最后一页就可以。
	        {
	            sc.changePaging(sc.getTotalPageCount(), sc.getPageSize());
	            result = mgr.searchByCriteria(sc);
	        }
	    }
		return result;
	}

	
	/**
	 * 新增方法
	 * @param request
	 * @param response
	 * @return
	 * @throws Exception
	 */
	public ModelAndView add(HttpServletRequest request,	HttpServletResponse response) throws Exception {
		T entity = formBackingObject(request);
		ServletRequestDataBinder binder = this.getDefaultBinder(request,entity);
		BindException errors = new BindException(binder.getBindingResult());
		return showForm(request, errors);
	}
	
	/**
	 * 编辑方法
	 * 
	 * @param request
	 * @param response
	 * @return
	 * @throws Exception
	 */
	public ModelAndView edit(HttpServletRequest request, HttpServletResponse response) throws Exception {
		T entity = formBackingObject(request);
		ServletRequestDataBinder binder = this.getDefaultBinder(request,entity);
		BindException errors = new BindException(binder.getBindingResult());
		return showForm(request, errors);
	}
	

	/**
	 * 删除方法
	 * @param request
	 * @param response
	 * @return
	 * @throws Exception
	 */
	public ModelAndView delete(HttpServletRequest request,	HttpServletResponse response) throws Exception {
		// 取得Form对应的Model。因为要先保存其名称，否则可能会连I18N数据也已经删除了；另外可以用于出错的时候恢复显示Form页面
		T entity = formBackingObject(request);
		
		// 删除并设置信息
		String entityId = request.getParameter(entityIdName);
		if (!StringUtils.isEmpty(entityId)) {
			// 先构造提示信息，否则信息对应的记录可能也会被删除。
			String entityName = getEntityName(entity);
			try {
				
				entity = mgr.deleteById(new Integer(entityId));
				
			} catch (DataIntegrityViolationException e) {
				e.printStackTrace();
				saveErrorMessage(request, this.getMessage("common.message.delete.constraintViolation"));
				
				// 为了出错的时候可以恢复表单显示，构造errors。无须作表单绑定和验证
				ServletRequestDataBinder binder = this.getDefaultBinder(request,entity);
				BindException errors = new BindException(binder.getBindingResult());
				
				return showForm(request, errors);
			}
			postDelete(request, entityName);
		} else {
			saveMessage(request, getMessage("errors.invalid.delete.id",	new Object[] { entityId }));
		}
		return getModelAndView(cancelFormView, null);
	}
	
	/**
	 * 删除后可做一些其他的操作，默认是封装删除成功信息
	 * 子类可以覆写此方法
	 * @param request
	 * @param entity
	 */
	protected void postDelete (HttpServletRequest request, String entityName){
		String message = getMessage("common.deleted", new Object[] {getEntityTypeMessage(), entityName });
		saveMessage(request, message);
	}
	
	/**
	 * 批量删除记录方法
	 * 批量处理的模式：应该重用单个实体处理的方法，否则，就会出现处理不一致的情况。本模式能保证事务完整性。成功后重定向到列表页面，以消除重复提交问题。
	 * 
	 * @param request
	 * @param response
	 */
	public ModelAndView multiDelete(HttpServletRequest request, 	HttpServletResponse response) {
		// 取得要删除的记录的id的列表。注意：选择框的id要是"multiIds"。
		String[] ids = request.getParameterValues("multiIds");
		if (ids != null && ids.length > 0) {
			try{
				mgr.deleteAllByIds(ids);
			}catch (DataIntegrityViolationException e) {
				//Hibernate底层仍会log此出错误信息
				this.saveErrorMessage(request, this.getMessage("common.message.delete.constraintViolation"));
				return this.returnToSearch(request, response);
			}
			String message = getMessage("common.deleted.multi", new Object[] {getEntityTypeMessage(), ids.length });
			saveMessage(request, message);
		}
		return getRedirectToActionView("search");
	}

	/**
	 * Prepare model and view for the given form, including reference and
	 * errors, adding a controller-specific control model.
	 * <p>
	 * Can be used in subclasses to redirect back to a specific form page.
	 * 
	 * @param errors  validation errors holder
	 * @return the prepared form view
	 */
	protected ModelAndView showForm(HttpServletRequest request, BindException errors) {
		// Fetch errors model as starting point, containing form object under
		// "commandName", and corresponding Errors instance under internal key.
		Map model = errors.getModel();
		if (isEntityNew(request) || errors.hasErrors()) {
			if (errors.hasErrors()) {
				//用于在saveAndNext方法中进行跳转
				request.setAttribute("HAS_ERRORS", Boolean.TRUE);
			}
			request.setAttribute("entityClassName", entityClassName);
		}
		ModelAndView mv = getModelAndView(formView, model);
		onShowForm(request, mv);
		return mv;
	}
	

	/**
	 * showFrom时调用,可以重载这个方法在Model上增加Command
	 * @param request
	 * @param mv
	 */
	protected void onShowForm(HttpServletRequest request, ModelAndView mv){
		
	}
	
	
	
	/**
	 * 说明：考虑不要保存浏览历史并依次返回的功能，比较复杂和难做好，而即使不提供这功能，用户也不会有明显的不便。返回到搜索列表通过指定的backToSearch功能实现。
	 * 所以对于Form，就返回列表；对于列表，就返回上层。如果想实现其他策略，重载本方法。
	 * @param request
	 * @param response
	 * @return
	 */
	public ModelAndView cancelForm(HttpServletRequest request, HttpServletResponse response) {
		return getModelAndView(cancelFormView, null);
	}


	/**
	 * 多条记录的批量更新，本模式并能保证事务完整性。Controller应该构造更新的model，然后传给Mgr更新，整个更新过程应该放在事务的一个方法进行。
	 * Controller不应该直接操作Entity。Mgr对Controller传入的Model采用Copy的方式。
	 * 对于复杂、多个实体的情况，应该采用类似模式，但要实现自己的方法（重载）。
	 * 
	 * @param request
	 * @param response
	 */
	public ModelAndView multiSave(HttpServletRequest request, HttpServletResponse response) {
		Map<Integer, Map<String, Object>> multiUpdateModel = getMultiSaveModel(request);
		if (multiUpdateModel != null && multiUpdateModel.size() > 0) {
			mgr.saveAll(multiUpdateModel);
			String message = getMessage("common.updated.multi");
			saveMessage(request, message);
		}
		return getRedirectToActionView("search");
	}
	
	/**
	 * 需要支持批量更新的时候需要重载此方法。
	 * <P>
	 * 本来这方法对大部分情况也是可以自动分析和转换的，但考虑到比较复杂和难以灵活（验证、缺省值、I18N等），暂时要求各模块自己实现。要求数据要先转换为正确的类型。
	 * 
	 * @param request
	 * @return
	 */
	protected abstract Map<Integer, Map<String, Object>> getMultiSaveModel(HttpServletRequest request);

	/**
	 * 子类需要实现此方法，进行保存之前的绑定、验证、转换等处理。
	 * 子类可覆盖此方法
	 * @param request
	 * @param entity
	 * @param errors
	 */
	protected void preSave(HttpServletRequest request, T entity, BindException errors){
	}

	/**
	 * 子类可覆盖此方法，进行保存成功之后的一些操作
	 * 
	 * @param request
	 * @param entity
	 */
	protected void postSave (HttpServletRequest request, T entity){
		//保存操作成功的提示信息
		String msgKey = (isEntityNew(request)) ? "common.added"	: "common.updated";
		String message = getMessage(msgKey, new Object[] { getEntityTypeMessage(), getEntityName(entity) });
		saveMessage(request, message);
	}


	/**
	 * 返回上次保存的搜索结果列表。
	 * <P>
	 * 存在问题：只能返回defaultAction，而不能返回之前的action（因为不同的action其实都可以进行搜索）
	 * 
	 * @param request
	 * @param response
	 * @return
	 */
	public ModelAndView returnToSearch(HttpServletRequest request, HttpServletResponse response) {
		return search(request, response);
	}

	/**
	 * 保存单个记录方法，并可以处理应用级的错误信息。
	 * 在formBackingObject读数据的时候已经加锁，所以可以保证事务和版本控制。子类需要实现onSave。
	 * @param request
	 * @param response
	 * @return
	 */
	public ModelAndView save(HttpServletRequest request, HttpServletResponse response) throws Exception {
		T entity = formBackingObject(request);
		ServletRequestDataBinder binder = bindAndValidate(request, entity);
		BindException errors = new BindException(binder.getBindingResult());
		try {
			preSave(request, entity, errors);
			if (!errors.hasErrors()) {
				mgr.save(entity);
				postSave(request, entity);
			}
		} catch (ApplicationException e) {
			handleApplicationException(errors, e);
		}

		ModelAndView mav;
		if (errors.hasErrors()) {
			mav = showForm(request, errors);
		} else if (successView != null) {
			mav = getModelAndView(successView, errors.getModel());
		} else {
			mav = getRedirectToActionView("edit", ((BaseObject) entity).getId().toString());
		}
		return mav;
	}

	
	/**
	 * 页数
	 * @param defaultPageSize   the defaultPageSize to set
	 */
	public void setDefaultPageSize(int defaultPageSize) {
		this.defaultPageSize = defaultPageSize;
	}

	/**
	 * 指定在Form页面的View。
	 * @param formView      the formView to set
	 */
	public void setFormView(String formView) {
		this.formView = formView;
	}
	
	/**
	 *  指定在Form页面，执行服务器端Cancel的时候用的View。注意：Delete操作也使用这个View。
	 * @param cancelFormView
	 *           
	 */
	public void setCancelFormView(String cancelFormView) {
		this.cancelFormView = cancelFormView;
	}


	/**
	 * 指定列表、搜索页面的View
	 * @param listView   the listView to set
	 */
	public void setListView(String listView) {
		this.listView = listView;
	}

	/**
	 * 当自动生成的url映射不适用的时候，可以指定一个映射的url；一般是用于操作完成后转向用。
	 * 
	 * @param mappedUrl
	 */
	public void setMappedUrl(String mappedUrl) {
		this.mappedUrl = mappedUrl;
	}

	/**
	 * @param parentView
	 *            the parentView to set
	 */
	public void setParentView(String parentView) {
		this.parentView = parentView;
	}

	/**
	 * 指定在Form页面，成功（没有验证错误等）执行保存或其他更新类的操作后的View。缺省是直接用formView。
	 * 
	 * @param successView
	 *            the successView to set
	 */
	public void setSuccessView(String successView) {
		this.successView = successView;
	}
	
	/**
	 * 用来区分新增还是更新的方法。
	 * @param request
	 * @return
	 */
	protected final boolean isEntityNew(HttpServletRequest request) {
		return StringUtils.isEmpty(request.getParameter(entityIdName));
	}

	
	
	protected String getEntityTypeMessage() {
		return getMessage(entityClassName + "Detail.heading");
	}
	
	/**
	 * 和Spring的SimpleFormController里面的方法的作用类似，取得当前form里面的command，在编辑之前应该先调用。
	 * @param request
	 * @return
	 */
	protected T formBackingObject(HttpServletRequest request) {
		String entityId = request.getParameter(entityIdName);
		T entity = null;

		if (!StringUtils.isEmpty(entityId)) {
			try {
				entity = mgr.loadById(new Integer(entityId));
			} catch (Exception ex) {
				logger.error(formatMsg("Error retrieving entity [%1 : %2]. So assume it is new.",entityClassName, entityId, ex));
				saveMessage(request, getMessage("errors.invalid.edit.id", new Object[] { entityId }));
			}
		}
		if (entity == null) {
			try {
				entity = entityClass.newInstance();
			} catch (Throwable e) {
				throw new RuntimeException("Unexpected error!", e);
			}
		}
		request.setAttribute("entityClassName", entityClassName);
		return entity;
	}


	/**
	 * 处理应用级异常
	 * @param request
	 * @param response
	 * @param exception
	 * @return
	 */
	public ModelAndView handleApplicationException(HttpServletRequest request, HttpServletResponse response, ApplicationException exception) {
		logger.error("Unhandled exception in controller processing.",exception);
		if (exception.getErrorCode() != null) {
			saveMessage(request, getMessage(exception.getErrorCode(), exception.getArgs()));
		} else {
			saveMessage(request, exception.getMessage());
		}
		return defaultAction(request, response);
	}
	
	/**
	 * 重定向指定的Action。
	 * <P>
	 * 说明：因为所有功能都基于action，而且在List页面和Form页面，form.action都是mappedUrl，不会带参数，所以重定向是个很好的方法，可以放心使用。
	 * 
	 * @param actionName
	 * @return
	 */
	protected ModelAndView getRedirectToActionView(String actionName) {
		return getRedirectView(UrlUtil.appendParamToUrl(mappedUrl, "doAction",actionName));
	}
	/**
	 * 重定向指定的Action带entityId
	 * 
	 * @param actionName
	 * @return
	 */
	protected ModelAndView getRedirectToActionView(String actionName, String entityId) {
		return getRedirectView(UrlUtil.appendParamToUrl(UrlUtil.appendParamToUrl(mappedUrl, "doAction", actionName), entityIdName, entityId));
	}
	
	/**
	 * 是否允许重用上一个列表搜索条件sc
	 * @param lastSearchViewAllowed
	 */
	public void setLastSearchViewAllowed(boolean lastSearchViewAllowed) {
		this.lastSearchViewAllowed = lastSearchViewAllowed;
	}
}
